feat(gui): animation, Preferences to the Colony convention, and the 57 shared palettes - #41
Conversation
The panes were a fixed 3:2, which is a guess about somebody else's screen. On a narrow window the right pane's tab strip runs out of room and clips the last tab - "Diagnostics (2)" was the one that prompted this - and there was nothing to do about it: the space between the lists was a gap, not a handle. It is a handle now. Six pixels, full height, the toolbars' muted line at rest and the panel border's burgundy while held, so a drag in progress is visible rather than inferred. The ratio is clamped to 0.15..0.85 in both the live drag and the settings parser. A pane dragged to zero takes the divider with it, and then neither pane can be recovered - so an out-of-range value in `settings.ini` reads as the default rather than as a preference somebody chose, the same policy `drag_scroll_speed` already applies. It is written to `settings.ini` on RELEASE, not on every pointer move: the drag emits a message per frame and the settings are a file. Nothing new was needed to carry it. `app.cursor` and `app.window` were already tracked, `mouse_area` was already imported, and `PointerAt` was already ambient - which matters, because resizing the furniture must not disarm a two-click confirmation armed on a row. Tested through the real message loop: the pointer alone does not resize, a grab makes it follow, both clamps hold at the edges, release stops the drag and hands the value to the preferences, and a move after release is inert again.
Three rules hold this together, and the first is what makes it affordable. **Nothing animated changes layout.** Every animation here interpolates a colour. A frame in flight never re-measures a row, never reflows the mod list and never touches the file tree - which matters, because a fully expanded Skyrim Data tree is six figures of rows and re-laying it out sixty times a second would be a stutter, not a flourish. Turning motion off therefore changes no spacing at all. **The clock only runs while something is moving.** `anim::animating` gates the frame subscription, so an idle window subscribes to no timer. The cost at rest is not small, it is zero - the same condition the drag-scroll, hover-expand and run-poll timers already use. **A phase ends by itself.** `Phase` holds the instant a transition began and derives everything from elapsed time, so nothing has to remember to switch it off, and a missed frame shows up as a jump forward rather than as an animation stuck half way. ## What moves The two tab strips cross-fade. Both ends of the blend come from iced's own `button::primary` and `button::secondary`, evaluated against the live theme, so no colour is named and the transition stays right on the dark palette and on any palette added later. The tab being LEFT runs the same transition backwards - without that the arriving tab fades in while the old one snaps, which reads as a glitch rather than as a transition. A new status message fades in, so a status that replaces another is visibly a new one rather than a word that quietly became a different word. It is armed by comparing `status` after each message rather than at the dozen places across `state.rs` that assign it: "remember to also start the fade" is a rule that would be forgotten by the second one. ## Motion is a setting, because it has to be `motion` in `settings.ini`, on unless explicitly off, with the toggle under Preferences -> Appearance. Off means every animated value is drawn at its destination on the first frame - not a quicker animation, none - and the frame timer is never subscribed. The Colony convention requires that a program which animates can be told not to; this is that. `AnimationTick` is ambient. It fires sixty times a second, so treating it as an action would not merely shorten an armed two-click confirmation's life, it would end it between the two clicks every time - the defect the pointer and the download tick already taught this window, at sixty times the rate. It joins the set assertion that exists to catch the next timer somebody adds. 273 GUI tests, 138 elsewhere, clippy clean on the pinned 1.94.1.
Measured against `design/settings-page.md` in Project-Colony-Resources, this page broke the convention's first structural rule and was missing one of its three mandatory categories. ## It was a modal, which is the one thing it must not be The convention names a modal, a separate window and a popover as the three counter-examples, and this was the first. It is a page now: it replaces the content area, and the header, menu bar, toolbar and status bar all stay exactly where they are - the program's chrome does not move because the user went to configure it. What that buys is not tidiness. The card was capped at 620x240, so every category was read through a 240-pixel slot however large the window was. The screen that most needs room to be scanned had the least of it. ## Accessibility was missing, and it is not optional "The Accessibility category is part of the skeleton, not a nice-to-have." The first three categories are imposed and ordered - General, Appearance, Accessibility - and this had five with the third absent. Motion moves there from Appearance, where the previous commit had put it. The distinction is not filing: Appearance is what the window looks like, Accessibility is what it does for somebody who needs it to do less. The convention also asks for a high-contrast palette, a dyslexia-friendly font and a text scale. Eidos has none of the three - every size in this window is a literal, so a scale is a change to the whole GUI rather than a setting. The category says so in as many words rather than showing switches that do nothing. ## The rest of the checklist - Titled **Preferences**, at 22, not "Settings" at 18. The convention settles the user-facing word; only the code is still named `settings_*`, which costs the user nothing. - `settings_dialog` is now `preferences_page`: a function whose name disagrees with what it returns is how the next reader is misled. - Each category opens with its own heading and one line saying what it changes. General's carries the contract that matters most: preferences are saved automatically. There is no Save button and there must not be one. - Close at 13, padding [6, 14]; rail buttons at [8, 14], widened to 148 so "Accessibility" does not resize the column. - Descriptions in a muted ink, so a category reads as titles with explanations rather than as two columns of equally loud text. ## Two deliberate deviations, both about scale The convention's chevrons are Nerd Font codepoints. Eidos bundles no Nerd Font - it uses PNG icons - so those would render as tofu; the existing triangles stay. Section titles are 14, not the convention's 15. That number assumes Colony's type scale, whose body text is 13 where Eidos's is 12, and 15 would leave a section title as loud as the category heading above it. What the convention specifies is the hierarchy - page, category, section, body - and this keeps it, at 22 / 16 / 14 / 12. Six new tests state the convention where a refactor would otherwise break it silently: the first three categories and their order, About last, every category explaining itself in words it does not repeat, General carrying the no-Save contract, and motion filed under Accessibility. The rank table is an exhaustive match, so adding a category without placing it does not compile. 278 GUI tests, clippy clean on the pinned 1.94.1.
The theme setting had never done anything. `theme()` ignored its `App` argument and returned the same parchment whatever was picked, so the Light / Dark / System list had changed no pixel since it was written. It is gone, and the key it stored is now an unknown key an old `settings.ini` carries harmlessly. In its place: **25 theme families, 57 palettes and the 8 accent overrides** from the Colony catalogue, generated from the design tokens in Project-Colony-Resources rather than copied here. Adding a family upstream needs no arm, no picker entry and no code in this repository - only a tag bump. ## One palette, filled from one of two places Every colour in this window now comes from one 38-field `ThemePalette`: either `PARCHMENT` - the look Eidos has always worn, written out as those same 38 fields instead of scattered across seventy literals - or one of the catalogue's 57. `theme.rs` is the only file allowed to name a colour; everywhere else asks the palette. A literal hex outside it is a bug, right on one theme and wrong on fifty-seven. The parchment stays the default and keeps its own row at the top of the picker. An upgrade repaints nobody's window, and a picker you cannot come back through is a trap. ## Two colours had to stop being fixed The origin tint - a plugin belonging to the selected mod - was a fixed pale blue. It is now the card tinted towards the accent, and the STRENGTH of the tint is chosen rather than fixed: on several palettes the selection is itself an accent-tinted card, and one ratio landed on top of it - on catppuccin/frappe the two came out 0.02 apart, which is to say identical. Five strengths are measured against the three tints this must never be confused with and the furthest wins. A test walks all 57 palettes and fails if any pair becomes confusable. The FOMOD installer's ink-on-burgundy was a fixed pale colour. On a palette whose accent is light that is unreadable, so it asks `contrast_on` which side can actually be read. ## High contrast, which the convention had been asking for Derived from whichever theme is on rather than shipped as a twin, so it works on the parchment and on all 57. It fills the Vision section the Accessibility category was carrying an apology for. It moves the ink and the lines, never the grounds - a high-contrast mode that repainted the backgrounds would be a different theme rather than the same one read more easily, and a test says so. ## Drawn here, not by the shared widgets `colony-ui` ships a theme picker and an accent picker. Both are built to Colony's type scale, whose body text is 13 where this window's is 12 - a picker a size and a half larger than the page around it. The catalogue is the valuable part and it is shared; the hundred lines that draw it are this program's. The accent list still comes from `ACCENT_OVERRIDES`, never a local copy: its ORDER is load-bearing across the ecosystem. `accent` stores a key or nothing. "Auto" is the absence of an override, never a colour - writing a resolved colour would freeze it against the theme it came from. 289 GUI tests, 39 suites elsewhere, clippy clean on the pinned 1.94.1. Every literal left in the GUI is inside a `#[cfg(test)]` block.
Third commit: the theme catalogueThe theme setting had never done anything. In its place: 25 families, 57 palettes, 8 accent overrides, from the Colony catalogue - generated from the design tokens rather than copied here. Adding a family upstream needs no arm, no picker entry and no code in this repo, only a tag bump. One palette, filled from one of two placesEvery colour now comes from one 38-field
The parchment stays the default and keeps its own row at the top of the picker. An upgrade repaints nobody's window, and a picker you cannot come back through is a trap. Two colours had to stop being fixedThe origin tint - a plugin belonging to the selected mod - was a fixed pale blue. It is now the card tinted toward the accent, and the strength of the tint is chosen rather than fixed: on several palettes the selection is itself an accent-tinted card, and one ratio landed on top of it. On The FOMOD installer's ink-on-burgundy was a fixed pale colour. On a palette whose accent is light that is unreadable, so it now asks High contrastDerived from whichever theme is on rather than shipped as a twin, so it works on the parchment and on all 57. It fills the Vision section that Accessibility was carrying an apology for. It moves the ink and the lines, never the grounds - a high-contrast mode that repainted the backgrounds would be a different theme rather than the same one read more easily. A test says so. Drawn here, not by the shared widgets
The accent list still comes from
Still not verified: the appearance. I have not opened the window. |
# Conflicts: # crates/eidos-gui/src/main.rs # crates/eidos-gui/src/modinfo.rs # crates/eidos-gui/src/state.rs # crates/eidos-gui/src/theme.rs # crates/eidos-gui/src/update.rs # crates/eidos-instance/src/settings.rs
Stacked on #40 - merge that first and this retargets to
mainon its own.Two commits. The second moves the setting the first introduced, which is why they travel together.
1. Animation
Three rules, and the first is what makes it affordable.
Nothing animated changes layout. Every animation interpolates a colour. A frame in flight never re-measures a row, never reflows the mod list and never touches the file tree - which matters, because a fully expanded Skyrim
Datatree is six figures of rows. A pleasant consequence: turning motion off changes no spacing anywhere.The clock only runs while something is moving.
anim::animatinggates the frame subscription, so an idle window subscribes to no timer at all. The cost at rest is not small, it is zero - the same condition the drag-scroll, hover-to-expand and run-poll timers already use.A phase ends by itself. It derives everything from the instant it began, so nothing has to remember to switch it off, and a missed frame is a jump forward rather than an animation stuck half way.
What moves
The two tab strips cross-fade. Both ends of the blend come from iced's own
button::primaryandbutton::secondaryevaluated against the live theme, so no colour is named and it stays correct on the dark palette and on any palette added later. The tab being left runs the transition backwards - without that the arriving tab fades in while the old one snaps, which reads as a glitch.A new status message fades in, armed by comparing
statusafter each message rather than at the dozen places acrossstate.rsthat assign it. "Remember to also start the fade" is a rule that would be forgotten by the second one.The trap this window has fallen into twice
AnimationTickis ambient. It fires sixty times a second, so treating it as an action would not shorten an armed two-click confirmation's life - it would end it between the two clicks, every time. The pointer taught this window that once and the download tick taught it again. It joins the set assertion that exists to catch the next timer somebody adds.2. Preferences, measured against the convention
Checked against
design/settings-page.mdin Project-Colony-Resources. The page broke the first structural rule and was missing one of the three mandatory categories.It was a modal, which is the one thing it must not be
The convention names a modal, a separate window and a popover as the three counter-examples. This was the first.
It is a page now: it replaces the content area, and the header, menu bar, toolbar and status bar all stay where they are - the chrome does not move because the user went to configure it.
What that buys is not tidiness. The card was capped at 620x240, so every category was read through a 240-pixel slot however large the window was. The screen that most needs room to be scanned had the least of it.
Accessibility was missing, and it is not optional
The first three categories are imposed and ordered - General, Appearance, Accessibility. This had five, with the third absent.
Motion moves there from Appearance, where commit 1 had put it. Not filing: Appearance is what the window looks like, Accessibility is what it does for somebody who needs it to do less.
The convention also asks for a high-contrast palette, a dyslexia-friendly font and a text scale. Eidos has none of the three - every size in this window is a literal, so a scale is a change to the whole GUI rather than a setting. The category says so in as many words rather than showing switches that do nothing.
The rest of the checklist
settings_dialogpreferences_pageTwo deliberate deviations, both about scale
The convention's chevrons are Nerd Font codepoints. Eidos bundles no Nerd Font - it uses PNG icons - so those would render as tofu. The existing triangles stay.
Section titles are 14, not 15. That number assumes Colony's type scale, whose body text is 13 where Eidos's is 12; 15 would leave a section title as loud as the category heading above it. What the convention specifies is the hierarchy, and this keeps it: 22 / 16 / 14 / 12.
Six of the new tests state the convention itself, so a later refactor breaks loudly: the imposed three and their order, About last, every category explaining itself in words it does not repeat, General carrying the no-Save contract, and motion filed under Accessibility. The rank table is an exhaustive match - adding a category without placing it does not compile.
Not verified: the appearance. I have not opened the window. The blend is asserted at its endpoints and midpoint, and the page structure by its data, not looked at.